﻿using Microsoft.Win32;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Toenda.Foundation.SystemLayer {
	/// <summary>
	/// Class Processor
	/// </summary>
	public static class Processor {
		#region "NESTED TYPES"
		// ---------------------------------------------------
		// NESTED TYPES
		// ---------------------------------------------------
		#endregion

		#region "INTERNAL VARIABLES"
		// ---------------------------------------------------
		// INTERNAL VARIABLES
		// ---------------------------------------------------

		private static int logicalCpuCount;
		private static string cpuName;

		#endregion

		#region "CONSTRUCTORS"
		// ---------------------------------------------------
		// CONSTRUCTORS
		// ---------------------------------------------------

		/// <summary>
		/// Initializes the <see cref="Processor"/> class.
		/// </summary>
		static Processor() {
			logicalCpuCount = ConcreteLogicalCpuCount;
		}

		#endregion

		#region "INTERFACE IMPLEMENTATIONS"
		// ---------------------------------------------------
		// INTERFACE IMPLEMENTATIONS
		// ---------------------------------------------------
		#endregion

		#region "PROPERTIES"
		// ---------------------------------------------------
		// PROPERTIES
		// ---------------------------------------------------
		#endregion

		#region "EVENTS"
		// ---------------------------------------------------
		// EVENTS
		// ---------------------------------------------------
		#endregion

		#region "PRIVATE MEMBERS"
		// ---------------------------------------------------
		// PRIVATE MEMBERS
		// ---------------------------------------------------

		/// <summary>
		/// Converts the specified w processor architecture.
		/// </summary>
		/// <param name="wProcessorArchitecture">The w processor architecture.</param>
		/// <returns></returns>
		private static ProcessorArchitecture Convert(ushort wProcessorArchitecture) {
			ProcessorArchitecture platform;

			switch(wProcessorArchitecture) {
				case NativeConstants.PROCESSOR_ARCHITECTURE_AMD64:
					platform = ProcessorArchitecture.X64;
					break;

				case NativeConstants.PROCESSOR_ARCHITECTURE_INTEL:
					platform = ProcessorArchitecture.X86;
					break;

				default:
				case NativeConstants.PROCESSOR_ARCHITECTURE_UNKNOWN:
					platform = ProcessorArchitecture.Unknown;
					break;
			}

			return platform;
		}

		/// <summary>
		/// Gets the name of the cpu.
		/// </summary>
		/// <returns></returns>
		private static string GetCpuName() {
			Guid processorClassGuid = new Guid("{50127DC3-0F36-415E-A6CC-4CB3BE910B65}");
			IntPtr hDiSet = IntPtr.Zero;
			string cpuName = null;

			try {
				hDiSet = NativeMethods.SetupDiGetClassDevsW(ref processorClassGuid, null, IntPtr.Zero, NativeConstants.DIGCF_PRESENT);

				if(hDiSet == NativeConstants.INVALID_HANDLE_VALUE) {
					NativeMethods.ThrowOnWin32Error("SetupDiGetClassDevsW returned INVALID_HANDLE_VALUE");
				}

				bool bResult = false;
				uint memberIndex = 0;

				while(true) {
					NativeStructs.SP_DEVINFO_DATA spDevinfoData = new NativeStructs.SP_DEVINFO_DATA();
					spDevinfoData.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SP_DEVINFO_DATA));

					bResult = NativeMethods.SetupDiEnumDeviceInfo(hDiSet, memberIndex, ref spDevinfoData);

					if(!bResult) {
						int error = Marshal.GetLastWin32Error();

						if(error == NativeConstants.ERROR_NO_MORE_ITEMS) {
							break;
						}
						else {
							throw new Win32Exception("SetupDiEnumDeviceInfo returned false, GetLastError() = " + error.ToString());
						}
					}

					uint lengthReq = 0;
					bResult = NativeMethods.SetupDiGetDeviceInstanceIdW(hDiSet, ref spDevinfoData, IntPtr.Zero, 0, out lengthReq);

					if(bResult) {
						NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(1) returned true");
					}

					if(lengthReq == 0) {
						NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(1) returned false, but also 0 for lengthReq");
					}

					IntPtr str = IntPtr.Zero;
					string regPath = null;

					try {
						// Note: We cannot use Memory.Allocate() here because this property is
						// usually retrieved during app shutdown, during which the heap may not
						// be available.
						str = Marshal.AllocHGlobal(checked((int)(sizeof(char) * (1 + lengthReq))));
						bResult = NativeMethods.SetupDiGetDeviceInstanceIdW(hDiSet, ref spDevinfoData, str, lengthReq, out lengthReq);

						if(!bResult) {
							NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(2) returned false");
						}

						regPath = Marshal.PtrToStringUni(str);
					}

					finally {
						if(str != IntPtr.Zero) {
							Marshal.FreeHGlobal(str);
							str = IntPtr.Zero;
						}
					}

					string keyName = @"SYSTEM\CurrentControlSet\Enum\" + regPath;
					using(RegistryKey procKey = Registry.LocalMachine.OpenSubKey(keyName, false)) {
						const string friendlyName = "FriendlyName";

						if(procKey != null) {
							object valueObj = procKey.GetValue(friendlyName);
							string value = valueObj as string;

							if(value != null) {
								cpuName = value;
							}
						}
					}

					if(cpuName != null) {
						break;
					}

					++memberIndex;
				}
			}

			finally {
				if(hDiSet != IntPtr.Zero) {
					NativeMethods.SetupDiDestroyDeviceInfoList(hDiSet);
					hDiSet = IntPtr.Zero;
				}
			}

			return cpuName;
		}

		#endregion

		#region "PROTECTED MEMBERS"
		// ---------------------------------------------------
		// PROTECTED MEMBERS
		// ---------------------------------------------------
		#endregion

		#region "PUBLIC MEMBERS"
		// ---------------------------------------------------
		// PUBLIC MEMBERS
		// ---------------------------------------------------

		/// <summary>
		/// Returns the processor architecture that the current process is using.
		/// </summary>
		/// <remarks>
		/// Note that if the current process is 32-bit, but the OS is 64-bit, this
		/// property will still return X86 and not X64.
		/// </remarks>
		public static ProcessorArchitecture Architecture {
			get {
				NativeStructs.SYSTEM_INFO sysInfo = new NativeStructs.SYSTEM_INFO();
				NativeMethods.GetSystemInfo(ref sysInfo);
				ProcessorArchitecture architecture = Convert(sysInfo.wProcessorArchitecture);
				return architecture;
			}
		}

		/// <summary>
		/// Returns the processor architecture of the installed operating system.
		/// </summary>
		/// <remarks>
		/// Note that this may differ from the Architecture property if, for instance,
		/// this is a 32-bit process on a 64-bit OS.
		/// </remarks>
		public static ProcessorArchitecture NativeArchitecture {
			get {
				NativeStructs.SYSTEM_INFO sysInfo = new NativeStructs.SYSTEM_INFO();
				NativeMethods.GetNativeSystemInfo(ref sysInfo);
				ProcessorArchitecture architecture = Convert(sysInfo.wProcessorArchitecture);
				return architecture;
			}
		}

		/// <summary>
		/// Returns the name of the CPU that is installed. If more than 1 CPU is installed,
		/// then the name of the first one is retrieved.
		/// </summary>
		/// <remarks>
		/// This is the name that shows up in Windows Device Manager in the "Processors" node.
		/// Note to implementors: This is only ever used for diagnostics (e.g., crash log).
		/// </remarks>
		public static string CpuName {
			get {
				if(cpuName == null) {
					cpuName = GetCpuName();
				}

				return cpuName;
			}
		}

		/// <summary>
		/// Gets the number of logical or "virtual" processors installed in the computer.
		/// </summary>
		/// <remarks>
		/// This value may not return the actual number of processors installed in the system.
		/// It may be set to another number for testing and benchmarking purposes. It is
		/// recommended that you use this property instead of ConcreteLogicalCpuCount for the
		/// purposes of optimizing thread usage.
		/// The maximum value for this property is 32 when running as a 32-bit process, or
		/// 64 for a 64-bit process. Note that this implies the maximum is 32 for a 32-bit process
		/// even when running on a 64-bit system.
		/// </remarks>
		public static int LogicalCpuCount {
			get {
				return logicalCpuCount;
			}

			set {
				if(value < 1 || value > (IntPtr.Size * 8)) {
					throw new ArgumentOutOfRangeException("value", value, "must be in the range [0, " + (IntPtr.Size * 8).ToString() + "]");
				}

				logicalCpuCount = value;
			}
		}

		/// <summary>
		/// Gets the number of logical or "virtual" processors installed in the computer.
		/// </summary>
		/// <remarks>
		/// This property will always return the actual number of logical processors installed
		/// in the system. Note that processors such as Intel Xeons and Pentium 4's with
		/// HyperThreading will result in values that are twice the number of physical processor
		/// packages that have been installed (i.e. 2 Xeons w/ HT => ConcreteLogicalCpuCount = 4).
		/// </remarks>
		public static int ConcreteLogicalCpuCount {
			get {
				return Environment.ProcessorCount;
			}
		}

		#endregion
	}
}
